<!DOCTYPE html>
            <html lang="en" data-theme="light">

                <head>
                    <meta charset="utf-8">

                    <title>The Book of Shaders</title>
                    <link href="/favicon.gif" rel="shortcut icon" />
                    <meta name="keywords" content="shader,openGL,WebGL,GLSL,book,procedural,generative" />
                    <meta name="description" content="Gentle step-by-step guide through the abstract and complex universe of Fragment Shaders." />

                    <!— Open Graph data —>
                    <meta property="og:type" content="article" />
                    <meta property="og:title" content="The Book of Shaders" />
                    <meta property="og:site_name" content="The Book of Shaders" />
                    <meta property="og:description" content="Gentle step-by-step guide through the abstract and complex universe of Fragment Shaders." />
                    <meta property="og:image" content="http://thebookofshaders.com/thumb.png" />
                    <meta property="og:image:secure_url" content="https://thebookofshaders.com/thumb.png" />
                    <meta property="og:image:type" content="image/png" />
                    <meta property="og:image:width" content="500" />
                    <meta property="og:image:height" content="500" />

                    <link href="/favicon.gif" rel="shortcut icon" />

                    <!-- Highlight -->
                    <link type="text/css" rel="stylesheet" href="/css/github.css">
                    <script type="text/javascript" src="/src/highlight.min.js"></script>
                    <!-- GlslCanvas -->
                    <script type="text/javascript" src="/src/glslCanvas/GlslCanvas.js"></script>

                    <!-- GlslEditor -->
                    <link type="text/css" rel="stylesheet" href="/src/glslEditor/glslEditor.css">
                    <script type="application/javascript" src="/src/glslEditor/glslEditor.js"></script>

                    <!-- GlslGallery -->
                    <link type="text/css" rel="stylesheet" href="/src/glslGallery/glslGallery.css">
                    <script type="application/javascript" src="/src/glslGallery/glslGallery.js"></script>

                    <!-- Main style -->
                    <link type="text/css" rel="stylesheet" href="/css/styles.css">

                </head>

                <body>
                    <div class="toc-header">
                        <p class="subtitle"><a href="https://thebookofshaders.com/">The Book of Shaders</a> by <a href="http://patriciogonzalezvivo.com">Patricio Gonzalez Vivo</a> & <a href="http://jenlowe.net">Jen Lowe</a> </p>
                        <p>  <a href=".">中文版</a> - <a href="https://thebookofshaders.com/">English</a></p>
                    </div>
                    <hr>

                    <div id="content"><p><img src="rangel.jpg" alt="Due East over Shadequarter Mountain - Matthew Rangel (2005) "></p>
<h2 id="分形布朗运动（fractal-brownian-motion）">分形布朗运动（Fractal Brownian Motion）</h2>
<p>噪声对不同的人来说有不同的意义。音乐家把它当成一种令人不安的声响，通信工程师把它当作干扰信号，天体物理学家把它看作宇宙微波背景辐射。这些概念吸引着我们去探索处处可见的随机性的物理原因。但是，让我们从更基础，也更简单的开始：波和波的属性。波就是某些属性随着时间波动变化。声波是气压的波动，电磁波是电场和磁场的波动。波的两个重要特征是振幅（amplitude）和频率（frequency）。一个简单的线性波（一维）的方程如下：</p>
<div class="simpleFunction" data="
float amplitude = 1.;
float frequency = 1.;
y = amplitude * sin(x * frequency);
"></div>

<ul>
<li>试着改变频率和振幅的值，理解他们如何影响波形。</li>
<li>使用造型函数，试着随时间改变振幅。</li>
<li>使用造型函数，试着随时间改变频率。</li>
</ul>
<p>通过做后两个练习，你已经知道怎么“调节”一个正弦波。恭喜你，你刚刚创造了一个 AM（调幅）和 FM（调频）波！</p>
<p>波的另一个有趣的特性是可以相加，这一特性的正式说法叫叠加性。调一调下面几行代码，注意我们加上那些不同振幅和频率的正弦波的时候，总的波形是如何变化的。</p>
<div class="simpleFunction" data="
float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
y += sin(x*frequency*2.221 + t*0.437)*5.0;
y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;
"></div>

<ul>
<li>试试改变加上去的波的振幅和频率。</li>
<li>有没有可能两个波正好相互抵消？如果是的话，会是什么样子？</li>
<li>有没有一种叠加波的方法，让他们互相放大？</li>
</ul>
<p>从音乐理论上说，每个音符都和一个特定的频率相关联。这些音符和频率的关系遵从一定的模式，也就是我们所说的音阶，一个八度（octave）对应着频率上的加倍或减半。</p>
<p>现在，让我们把正弦波放在一边，想想 Perlin 噪声！Perlin 噪声的基本形式看起来和正弦波有点相似。它的振幅和频率有着某种变化，但振幅保持着合理的连续性，而且频率被限制在一个距离中心频率很小的范围内。尽管它不像正弦波那样规则，并且把几个不同缩放比例的 Perlin 噪声相加更容易制造出随机形态。把一些正弦波相加也是有可能制造随机形态的，但那需要很多不同的波叠加才能把他们的天生的周期性和规则性隐藏起来。</p>
<p>通过在循环（循环次数为 <em>octaves</em>，一次循环为一个八度）中叠加噪声，并以一定的倍数（<em>lacunarity</em>，间隙度）连续升高频率，同时以一定的比例（<em>gain</em>，增益）降低 <strong>噪声</strong> 的振幅，最终的结果会有更好的细节。这项技术叫“分形布朗运动（fractal Brownian Motion）”（<em>fBM</em>），或者“分形噪声（fractal noise）”，最简单的实现如下：</p>
<div class="simpleFunction" data="// Properties
const int octaves = 1;
float lacunarity = 2.0;
float gain = 0.5;
//
// Initial values
float amplitude = 0.5;
float frequency = 1.;
//
// Loop of octaves
for (int i = 0; i < octaves; i++) {
&#9;y += amplitude * noise(frequency*x);
&#9;frequency *= lacunarity;
&#9;amplitude *= gain;
}"></div>

<ul>
<li>从 1 到 2，4，8，10 逐渐改变 octaves，看看会发生什么。</li>
<li>当 octaves 大于 4 时，尝试改变 lacunarity 的值。</li>
<li>当 octaves 大于 4 时，改变 gain 的值，看看会发生什么。</li>
</ul>
<p>注意，随着我们一个八度接一个八度地往上叠加，曲线看起来有越来越多的细节，同时，自相似性也越来越明显。如果你放大看看，曲线的局部和整体看起来很相似，并且，任选两段不同的部分看起来也多少有些相似。这是一个数学上的分形的重要性质，我们在上面的循环中模拟了这个性质。我们不是要创造一个<em>真的</em>分形，因为我们在几次循环之后就不再往上叠加了，但理论上说，如果我们一直继续这个循环，不断地往上叠加噪声，就会得到一个真正的数学意义上的分形。在计算机图形领域，我们能处理的细节总是有极限的，比如物体比一个像素还小的时候，所以没有必要不断地往上叠加来制造分形的形态。有时候我们确实需要叠加很多次，但不必叠加无限次。</p>
<p>下面的示例代码就是 fBm 的二维实现，生成了分形图案：</p>
<div class='codeAndCanvas' data='2d-fbm.frag'></div>

<ul>
<li>在 37 行减小八度（OCTAVES）的数量</li>
<li>在 47 行调试 fBm 的间隙度（lacumarity）</li>
<li>在 47 行调试 fBm 的增益（gain）</li>
</ul>
<p>这项技术被广泛地应用于构造程序化风景。fBm 的自相似性能够很完美地模拟山脉，因为山脉形成过程中的腐蚀形成了这种不同尺度上的自相似性。如果你对此感兴趣，你一定要去看看 <a href="http://www.iquilezles.org/www/articles/morenoise/morenoise.htm">Inigo Quiles 这篇关于高级噪声的文章</a>。</p>
<p><img src="holdsworth.jpg" alt="Blackout - Dan Holdsworth (2010)"></p>
<p>使用相同的技术，也可以获得其他效果，比如<strong>湍流</strong>（turbulence）效果。它本质上是一个 fBm，但是由一个有符号噪声的绝对值构成，从而在函数中创建了尖锐的山谷。</p>
<pre><code class="language-glsl">for (int i = 0; i &lt; OCTAVES; i++) {
    value += amplitude * abs(snoise(st));
    st *= 2.;
    amplitude *= .5;
}
</code></pre>
<p><a href="../edit.php#13/turbulence.frag"><img src="turbulence-long.png"  width="520px" height="200px"></img></a></p>
<p>这个算法家族中的另一个成员是<strong>山脊</strong>（ridge），就是把凹下去的山谷翻上来，从而制造山脊：</p>
<pre><code class="language-glsl">    n = abs(n);     // create creases
    n = offset - n; // invert so creases are at top
    n = n * n;      // sharpen creases
</code></pre>
<p><a href="../edit.php#13/ridge.frag"><img src="ridge-long.png"  width="520px" height="200px"></img></a></p>
<p>这个算法的另外一个变种，把噪声分量乘起来（而不是叠加），可以创造一些很有用的东西。另外一个方法是，根据前一次循环中的噪声来缩放后续的噪声。当我们这样做的时候，我们已经走出严格的分形定义了，走入了一个叫“多重分形”的未知领域。多重分形虽不是按数学方式严格定义，但这并不意味着它的用处会更少些。 实际上，用多重分形模拟生成地形在商业软件中非常常见。要了解更多，你可以去读 Kenton Musgrave 的“Texturing and Modeling: a Procedural Approach”（第三版）的 16 章。很可惜，这本书几年前已经绝版，不过你还可以从图书馆和二手市场找到。（网上有卖这本书第一版的 PDF 版，但是别去买——只是浪费钱，是 1994 年的版本，不包括第三版包含的地形建模的部分。）</p>
<h3 id="域翘曲（domain-warping）">域翘曲（Domain Warping）</h3>
<p><a href="http://www.iquilezles.org/www/articles/warp/warp.htm">Inigo Quiles 写了另一篇有趣的文章</a>，关于如何用 fBm 来扭曲 fBm 空间。很有意思，不是吗？这就像《盗梦空间》里的梦中梦。</p>
<p><img src="quiles.jpg" alt=" f(p) = fbm( p + fbm( p + fbm( p ) ) ) - Inigo Quiles (2002)"></p>
<p>下面的代码展示了这项技术的一个不那么极端的例子，用它生成类似云一样的纹理。注意自相似性如何表现在结果中。</p>
<div class='codeAndCanvas' data='clouds.frag'></div>

<p>用这种方法用噪声扭曲纹理坐标非常有用，非常有趣，也极难掌握。这是个很强大的工具，但要想用好它需要一些经验。一个有用的办法是，用噪声的导数（梯度）替换坐标。<a href="http://evasion.imag.fr/Publications/2001/PN01/">Ken Perlin 和 Fabrice Neyret 的一篇非常著名的“流噪声”</a>就是以这个想法为基础。一些现代的 Perlin 噪声的实现不但计算噪声，还计算它的解析梯度。如果“真实”梯度对过程化函数来说不便计算，你总是可以计算出数值梯度来逼近它，尽管没那么精确而且要花费更多工夫。</p>
</div>

                    <hr>
                    <ul class="navigationBar" >
                            <li class="navigationBar" onclick="previusPage()">&lt; &lt; Previous</li>
                            <li class="navigationBar" onclick="homePage()"> Home </li>
                            <li class="navigationBar" onclick="nextPage()">Next &gt; &gt;</li>
                        </ul>

                    <footer>
                        <p> Copyright 2015 <a href="http://www.patriciogonzalezvivo.com" target="_blank">Patricio Gonzalez Vivo</a> </p>
                    </footer>

                    <script src="/src/three.min.js"></script>

                    <script id="vertexShader" type="x-shader/x-vertex">
                        void main() {
                            gl_Position = vec4( position, 1.0 );
                        }
                    </script>

                    <script id="fragmentShader" type="x-shader/x-fragment">
                        uniform vec2 u_resolution;
                        uniform vec2 u_mouse;
                        uniform float u_time;

                        void main() {
                            vec2 st = gl_FragCoord.xy/u_resolution.xy;
                            gl_FragColor=vec4(st.x,st.y,0.0,1.0);
                        }
                    </script>

                    <script>
                        var camera, scene, renderer, clock;
                        var uniforms;
                        var mouse = { x: 0, y: 0 };

                        document.onmousemove = getMouseXY;

                        function getMouseXY (e) {
                            mouse.x = e.pageX;
                            mouse.y = e.pageY;
                        }

                        function init () {

                            camera = new THREE.Camera();
                            camera.position.z = 1;

                            scene = new THREE.Scene();
                            clock = new THREE.Clock();

                            var geometry = new THREE.PlaneBufferGeometry(2, 2);

                            uniforms = {
                                u_time: { type: "f", value: 1.0 },
                                u_mouse: { type: "v2", value: new THREE.Vector2() },
                                u_resolution: { type: "v2", value: new THREE.Vector2() }
                            };

                            var material = new THREE.ShaderMaterial({
                                uniforms: uniforms,
                                vertexShader: document.getElementById('vertexShader').textContent,
                                fragmentShader: document.getElementById('fragmentShader').textContent
                            });

                            var mesh = new THREE.Mesh(geometry, material);
                            scene.add(mesh);

                            renderer = new THREE.WebGLRenderer();
                            renderer.setPixelRatio(window.devicePixelRatio);

                            container.appendChild(renderer.domElement);

                            onWindowResize();
                            window.addEventListener('resize', onWindowResize, false);
                        }

                        function onWindowResize (event) {
                            renderer.setSize(window.innerWidth, window.innerHeight);
                            uniforms.u_resolution.value.x = renderer.domElement.width;
                            uniforms.u_resolution.value.y = renderer.domElement.height;
                            uniforms.u_mouse.value.x = mouse.x;
                            uniforms.u_mouse.value.y = mouse.y;
                        }

                        function animate () {
                            requestAnimationFrame(animate);
                            render();
                        }

                        function render () {
                            uniforms.u_time.value += clock.getDelta();
                            renderer.render(scene, camera);
                        }

                        var container = document.getElementById('container');

                        if (container) {

                            init();
                            animate();
                        }
                    </script>
                    <script type="text/javascript" src="/src/main.js" defer></script>
                </body>

            </html>